﻿using System.Web.Mvc;
using System.Web.Mvc.Html;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Web.Routing;

namespace MVCHelperExtensions
{
  /// <summary>
  /// This class contains extension methods for the ASP.NET MVC HtmlHelper class
  /// </summary>
  public static class HTMLHelperExtensions
  {
    #region Details

    /// <summary>
    /// Generates HTML for a link to an appropriate controller and details action
    /// based on the given model instance.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="helper"></param>
    /// <param name="linkText">text to be rendered inside the HTML address tag</param>
    /// <param name="modelInstance">model instance</param>
    /// <returns>HTML string with a hyperlink to the details view of the given model instance</returns>
    public static string LinkTo<TModel>(this HtmlHelper helper, string linkText, TModel modelInstance)
    {
      return LinkTo(helper, linkText, modelInstance, null, null);
    }

    /// <summary>
    /// Generates HTML for a link to an appropriate controller and details action
    /// based on the given model instance.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="helper"></param>
    /// <param name="linkText">text to be rendered inside the HTML address tag</param>
    /// <param name="modelInstance">model instance</param>
    /// <param name="routeValues">Additional route values to add to include in URL generation</param>
    /// <returns>HTML string with a hyperlink to the details view of the given model instance</returns>
    public static string LinkTo<TModel>(this HtmlHelper helper, string linkText, TModel modelInstance, object routeValues)
    {
      return LinkTo(helper, linkText, modelInstance, routeValues, null);
    }

    /// <summary>
    /// Generates HTML for a link to an appropriate controller and details action
    /// based on the given model instance.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="helper"></param>
    /// <param name="linkText">text to be rendered inside the HTML address tag</param>
    /// <param name="modelInstance">model instance</param>
    /// <param name="routeValues">Additional route values to add to include in URL generation</param>
    /// <param name="HTMLAttributes">Attributes to add to the hyperlink tag</param>
    /// <returns>HTML string with a hyperlink to the details view of the given model instance</returns>
    public static string LinkTo<TModel>(this HtmlHelper helper, string linkText, TModel modelInstance, object routeValues, object HTMLAttributes)
    {
      int modelID;
      Type modelType = modelInstance.GetType();

      // Determine Action Name
        string actionName = null;

      // Check the class attributes for a custom details action name, otherwise assign a default name
      MVCHelperDetailsMethodNameAttribute detailsNameAttribute =
        MVCHelperUtilities.GetClassAttribute<MVCHelperDetailsMethodNameAttribute>(modelType);
      if (detailsNameAttribute != null)
      {
        actionName = detailsNameAttribute.DetailsMethodName;
      }
      else
      {
        actionName = MVCHelpersDefaultValues.DETAILS_ACTION_NAME_DEFAULT;
      }

      // Find model instance ID
      modelID = MVCHelperUtilities.GetModelID<TModel>(modelInstance);

      RouteValueDictionary routeValueDictionary = new RouteValueDictionary(routeValues);
      routeValueDictionary.Add("id", modelID);

      IDictionary HTMLAttributeDictionary = MVCHelperUtilities.ObjectToDictionary(HTMLAttributes);
            
      // Find Controller name
      // If the controller name is null then the current controller will be used by default
      string controllerName = MVCHelperUtilities.GetControllerName(modelType);

      // Generate the HTML using an MVC built-in ActionLink method
      string linkHTML = helper.ActionLink(
          linkText,
          actionName,
          controllerName,
          routeValueDictionary, 
          (IDictionary<string,object>/*cast needed to invoke correct method*/)HTMLAttributeDictionary
        );
      
      return linkHTML;
    }

    /// <summary>
    /// Generates HTML for a link to an appropriate controller and details action
    /// based on the given model instance.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="helper"></param>
    /// <param name="linkText">text to be rendered inside the HTML address tag</param>
    /// <param name="modelInstance">model instance</param>
    /// <returns>HTML string with a hyperlink to the details view of the given model instance</returns>
    /// <remarks>
    /// Alias for LinkTo
    /// </remarks>
    public static string LinkToDetails<TModel>(this HtmlHelper helper, string linkText, TModel modelInstance)
    {
      return LinkTo(helper, linkText, modelInstance, null, null);
    }

    /// <summary>
    /// Generates HTML for a link to an appropriate controller and details action
    /// based on the given model instance.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="helper"></param>
    /// <param name="linkText">text to be rendered inside the HTML address tag</param>
    /// <param name="modelInstance">model instance</param>
    /// <param name="routeValues">Additional route values to add to include in URL generation</param>
    /// <returns>HTML string with a hyperlink to the details view of the given model instance</returns>
    /// <remarks>
    /// Alias for LinkTo
    /// </remarks>
    public static string LinkToDetails<TModel>(this HtmlHelper helper, string linkText, TModel modelInstance, object routeValues)
    {
      return LinkTo(helper, linkText, modelInstance, routeValues, null);
    }

    /// <summary>
    /// Generates HTML for a link to an appropriate controller and details action
    /// based on the given model instance.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="helper"></param>
    /// <param name="linkText">text to be rendered inside the HTML address tag</param>
    /// <param name="modelInstance">model instance</param>
    /// <param name="routeValues">Additional route values to add to include in URL generation</param>
    /// <param name="HTMLAttributes">Attributes to add to the hyperlink tag</param>
    /// <returns>HTML string with a hyperlink to the details view of the given model instance</returns>
    /// <remarks>
    /// Alias for LinkTo
    /// </remarks>
    public static string LinkToDetails<TModel>(this HtmlHelper helper, string linkText, TModel modelInstance, object routeValues, object HTMLAttributes)
    {
      return LinkTo(helper, linkText, modelInstance, routeValues, HTMLAttributes);
    }
    #endregion

    #region Index

    /// <summary>
    /// Determines the index path for the given model class
    /// </summary>
    /// <param name="helper"></param>
    /// <param name="modelType"></param>
    /// <param name="linkText">Text to be rendered inside the link tag</param>
    /// <returns>HTML string with a hyperlink to the index page for the given class</returns>
    /// <example>
    /// In an ASP.NET MVC view where Model is an instance of YourModelClass:
    /// <code><![CDATA[
    /// <%= HTML.LinkToIndex(typeof(Model), "click here"); %>
    /// ]]></code>
    /// </example>
    public static string LinkToIndex(this HtmlHelper helper, Type modelType, string linkText)
    {
      return LinkToIndex(helper, modelType, linkText, null, null);
    }

    /// <summary>
    /// Determines the index path for the given model class
    /// </summary>
    /// <param name="helper"></param>
    /// <param name="modelType"></param>
    /// <param name="linkText">Text to be rendered inside the link tag</param>
    /// <param name="routeValues">Additional route values to add to include in URL generation</param>
    /// <returns>HTML string with a hyperlink to the index page for the given class</returns>
    /// <example>
    /// In an ASP.NET MVC view where Model is an instance of YourModelClass:
    /// <code><![CDATA[
    /// <%= HTML.LinkToIndex(typeof(Model), "click here", new { urlparam = "example" }); %>
    /// ]]></code>
    /// </example>
    public static string LinkToIndex(this HtmlHelper helper, Type modelType, string linkText, object routeValues)
    {
      return LinkToIndex(helper, modelType, linkText, routeValues, null);
    }

    /// <summary>
    /// Determines the index path for the given model class
    /// </summary>
    /// <param name="helper"></param>
    /// <param name="modelType"></param>
    /// <param name="linkText">Text to be rendered inside the link tag</param>
    /// <param name="routeValues">Additional route values to add to include in URL generation</param>
    /// <param name="HTMLAttributes">Attributes to add to the hyperlink tag</param>
    /// <returns>HTML string with a hyperlink to the index page for the given class</returns>
    /// <example>
    /// In an ASP.NET MVC view where Model is an instance of YourModelClass:
    /// <code><![CDATA[
    /// <%= HTML.LinkToIndex(typeof(Model), "click here", new { controller = "other_controller" }, new { urlparam = "example" }); %>
    /// ]]></code>
    /// </example>
    public static string LinkToIndex(this HtmlHelper helper, Type modelType, string linkText, object routeValues, object HTMLAttributes)
    {
      // Determine Action Name
      string actionName = null;

      MVCHelperIndexMethodNameAttribute indexNameAttribute = 
        MVCHelperUtilities.GetClassAttribute<MVCHelperIndexMethodNameAttribute>(modelType);
      if (indexNameAttribute != null)
      {
        actionName = indexNameAttribute.IndexMethodName;
      }
      else
      {
        actionName = MVCHelpersDefaultValues.INDEX_ACTION_NAME_DEFAULT;
      }

      // Get controller name
      string controllerName = MVCHelperUtilities.GetControllerName(modelType);

      // Generate the HTML using an MVC built-in ActionLink method
      string linkHTML = helper.ActionLink(linkText, actionName, controllerName, routeValues, HTMLAttributes);

      return linkHTML;
    }

    /// <summary>
    /// Generates an HTML link to the index view of the given class
    /// </summary>
    /// <typeparam name="TModel">Class from which to derive the controller and action names</typeparam>
    /// <param name="helper"></param>
    /// <param name="linkText">Text to be rendered inside the link tag</param>
    /// <returns>HTML string with a hyperlink to the index view for given model class</returns>
    /// <example>
    /// In an ASP.NET MVC view:
    /// <code><![CDATA[
    /// <%= HTML.LinkToIndex<YourModelClass>("click here"); %>
    /// ]]></code>
    /// </example>
    public static string LinkToIndex<TModel>(this HtmlHelper helper, string linkText)
    {
      return LinkToIndex(helper, typeof(TModel), linkText);
    }

    /// <summary>
    /// Generates an HTML link to the index view of the given class
    /// </summary>
    /// <typeparam name="TModel">Class from which to derive the controller and action names</typeparam>
    /// <param name="helper"></param>
    /// <param name="linkText">Text to be rendered inside the link tag</param>
    /// <param name="routeValues">Additional route values to add to include in URL generation</param>
    /// <returns></returns>
    public static string LinkToIndex<TModel>(this HtmlHelper helper, string linkText, object routeValues)
    {
      return LinkToIndex(helper, typeof(TModel), linkText, routeValues, null);
    }

    /// <summary>
    /// Generates an HTML link to the index view of the given class
    /// </summary>
    /// <typeparam name="TModel">Class from which to derive the controller and action names</typeparam>
    /// <param name="helper"></param>
    /// <param name="linkText">Text to be rendered inside the link tag</param>
    /// <param name="routeValues">Additional route values to add to include in URL generation</param>
    /// <param name="HTMLAttributes">Attributes to add to the hyperlink tag</param>
    /// <returns></returns>
    public static string LinkToIndex<TModel>(this HtmlHelper helper, string linkText, object routeValues, object HTMLAttributes)
    {
      return LinkToIndex(helper, typeof(TModel), linkText, routeValues, HTMLAttributes);
    } 

    #endregion

    #region Edit
    /// <summary>
    /// Generates HTML for a link to an appropriate controller and edit action
    /// based on the given model instance.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="helper"></param>
    /// <param name="linkText">text to be rendered inside the HTML address tag</param>
    /// <param name="modelInstance">model instance</param>
    /// <returns>HTML string with a hyperlink to the edit view of the given model instance</returns>
    public static string LinkToEdit<TModel>(this HtmlHelper helper, string linkText, TModel modelInstance)
    {
      return LinkToEdit(helper, linkText, modelInstance, null, null);
    }
    
    /// <summary>
    /// Generates HTML for a link to an appropriate controller and edit action
    /// based on the given model instance.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="helper"></param>
    /// <param name="linkText">text to be rendered inside the HTML address tag</param>
    /// <param name="modelInstance">model instance</param>
    /// <param name="routeValues">Anonymous type containing additional route values to pass for URL generation.</param>
    /// <returns>HTML string with a hyperlink to the edit view of the given model instance</returns>
    public static string LinkToEdit<TModel>(this HtmlHelper helper, string linkText, TModel modelInstance, object routeValues)
    {
      return LinkToEdit(helper, linkText, modelInstance, routeValues, null);
    }

    /// <summary>
    /// Generates HTML for a link to an appropriate controller and edit action
    /// based on the given model instance.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="helper"></param>
    /// <param name="linkText">text to be rendered inside the HTML address tag</param>
    /// <param name="modelInstance">model instance</param>
    /// <param name="routeValues">Anonymous type containing additional route values to pass for URL generation.</param>
    /// <param name="HTMLAttributes">Anonymous type containing attribute value pairs to be applied to the link tag that is returned</param>
    /// <returns>HTML string with a hyperlink to the edit view of the given model instance</returns>
    public static string LinkToEdit<TModel>(this HtmlHelper helper, string linkText, TModel modelInstance, object routeValues, object HTMLAttributes)
    {
      Type modelType = modelInstance.GetType();

      // Get the name for the edit action for the given model type
      string editActionName;
      var editMethodNameAttribute = MVCHelperUtilities.GetClassAttribute<MVCHelperEditMethodNameAttribute>(modelType);
      if (editMethodNameAttribute != null)
        editActionName = editMethodNameAttribute.EditMethodName;
      else
        editActionName = MVCHelpersDefaultValues.EDIT_ACTION_NAME_DEFAULT;

      // Get the model instance id
      int modelID = MVCHelperUtilities.GetModelID(modelInstance);

      // Get controller name
      string controllerName = MVCHelperUtilities.GetControllerName(modelType);

      // Add the model id to the given route values.  This requires converting the route values
      // which are an object of anonymous type
      RouteValueDictionary routeValueDictionary = new RouteValueDictionary(routeValues);
      routeValueDictionary.Add("id", modelID);

      IDictionary HTMLAttributeDictionary = MVCHelperUtilities.ObjectToDictionary(HTMLAttributes);

      return helper.ActionLink(
        linkText, 
        editActionName,
        controllerName,
        routeValueDictionary,
        (IDictionary<string, object>/*cast needed to invoke correct method*/)HTMLAttributeDictionary
      );
    }
    #endregion

    #region New


    /// <summary>
    /// Determines the New path for the given model class
    /// </summary>
    /// <param name="helper"></param>
    /// <param name="modelType"></param>
    /// <param name="linkText">Text to be rendered inside the link tag</param>
    /// <returns>HTML string with a hyperlink to the New page for the given class</returns>
    /// <example>
    /// In an ASP.NET MVC view where Model is an instance of YourModelClass:
    /// <code><![CDATA[
    /// <%= HTML.LinkToNew(typeof(Model), "click here"); %>
    /// ]]></code>
    /// </example>
    public static string LinkToNew(this HtmlHelper helper, Type modelType, string linkText)
    {
      return LinkToNew(helper, modelType, linkText, null, null);
    }

    /// <summary>
    /// Determines the New path for the given model class
    /// </summary>
    /// <param name="helper"></param>
    /// <param name="modelType"></param>
    /// <param name="linkText">Text to be rendered inside the link tag</param>
    /// <param name="routeValues">Additional route values to add to include in URL generation</param>
    /// <returns>HTML string with a hyperlink to the New page for the given class</returns>
    /// <example>
    /// In an ASP.NET MVC view where Model is an instance of YourModelClass:
    /// <code><![CDATA[
    /// <%= HTML.LinkToNew(typeof(Model), "click here", new { urlparam = "example" }); %>
    /// ]]></code>
    /// </example>
    public static string LinkToNew(this HtmlHelper helper, Type modelType, string linkText, object routeValues)
    {
      return LinkToNew(helper, modelType, linkText, routeValues, null);
    }

    /// <summary>
    /// Determines the New path for the given model class
    /// </summary>
    /// <param name="helper"></param>
    /// <param name="modelType"></param>
    /// <param name="linkText">Text to be rendered inside the link tag</param>
    /// <param name="routeValues">Additional route values to add to include in URL generation</param>
    /// <param name="HTMLAttributes">Attributes to add to the hyperlink tag</param>
    /// <returns>HTML string with a hyperlink to the New page for the given class</returns>
    /// <example>
    /// In an ASP.NET MVC view where Model is an instance of YourModelClass:
    /// <code><![CDATA[
    /// <%= HTML.LinkToNew(typeof(Model), "click here", new { controller = "other_controller" }, new { urlparam = "example" }); %>
    /// ]]></code>
    /// </example>
    public static string LinkToNew(this HtmlHelper helper, Type modelType, string linkText, object routeValues, object HTMLAttributes)
    {
      // Determine Action Name
      string actionName = null;

      MVCHelperNewMethodNameAttribute NewNameAttribute =
        MVCHelperUtilities.GetClassAttribute<MVCHelperNewMethodNameAttribute>(modelType);
      if (NewNameAttribute != null)
      {
        actionName = NewNameAttribute.NewMethodName;
      }
      else
      {
        actionName = MVCHelpersDefaultValues.NEW_ACTION_NAME_DEFAULT;
      }

      // Get controller name
      string controllerName = MVCHelperUtilities.GetControllerName(modelType);

      // Generate the HTML using an MVC built-in ActionLink method
      // TODO: Allow routeValues to override actionName and controllerName
      string linkHTML = helper.ActionLink(linkText, actionName, controllerName, routeValues, HTMLAttributes);

      return linkHTML;
    }

    /// <summary>
    /// Generates an HTML link to the New view of the given class
    /// </summary>
    /// <typeparam name="TModel">Class from which to derive the controller and action names</typeparam>
    /// <param name="helper"></param>
    /// <param name="linkText">Text to be rendered inside the link tag</param>
    /// <returns>HTML string with a hyperlink to the New view for given model class</returns>
    /// <example>
    /// In an ASP.NET MVC view:
    /// <code><![CDATA[
    /// <%= HTML.LinkToNew<YourModelClass>("click here"); %>
    /// ]]></code>
    /// </example>
    public static string LinkToNew<TModel>(this HtmlHelper helper, string linkText)
    {
      return LinkToNew(helper, typeof(TModel), linkText);
    }

    /// <summary>
    /// Generates an HTML link to the New view of the given class
    /// </summary>
    /// <typeparam name="TModel">Class from which to derive the controller and action names</typeparam>
    /// <param name="helper"></param>
    /// <param name="linkText">Text to be rendered inside the link tag</param>
    /// <param name="routeValues">Additional route values to add to include in URL generation</param>
    /// <returns></returns>
    public static string LinkToNew<TModel>(this HtmlHelper helper, string linkText, object routeValues)
    {
      return LinkToNew(helper, typeof(TModel), linkText, routeValues, null);
    }

    /// <summary>
    /// Generates an HTML link to the New view of the given class
    /// </summary>
    /// <typeparam name="TModel">Class from which to derive the controller and action names</typeparam>
    /// <param name="helper"></param>
    /// <param name="linkText">Text to be rendered inside the link tag</param>
    /// <param name="routeValues">Additional route values to add to include in URL generation</param>
    /// <param name="HTMLAttributes">Attributes to add to the hyperlink tag</param>
    /// <returns></returns>
    public static string LinkToNew<TModel>(this HtmlHelper helper, string linkText, object routeValues, object HTMLAttributes)
    {
      return LinkToNew(helper, typeof(TModel), linkText, routeValues, HTMLAttributes);
    } 



    #endregion
  }
}
